#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdarg.h>
#include "token.h"
#include "scan.h"
#include "iexec.h"
#ifdef WIN32
#include "def_for_win.h"
#endif //WIN32

i_exec_t* g_exec = NULL;

char g_info[STR_BUF_SIZE];

#define FILE_BUF_SIZE 1024
#define CHAR_LF '\n'
/*
 * file_stream object
 */
typedef struct tag_file_stream_t {
   FILE*    file;
   int      line_no;
   char     buf[FILE_BUF_SIZE];
   int      cur_pos;
   int      total_chars;
   int      using_prev_char; /* if NonZero, get_char, returns prev_char.*/
   char     prev_char;
   int      prev_line_no;
} file_stream_t;

/*
 * File stream helper routines.
 */
void init_file_stream (file_stream_t* f, FILE* file);
/* return 0 on EOF*/
int get_char (file_stream_t* f, char* c);
void put_back (file_stream_t* f);


/*
 * File stream helper routines.
 */
void init_file_stream (file_stream_t* fs, FILE* file)
{
   memset (fs, 0, sizeof(file_stream_t) );
   fs->file = file;
   fs->line_no = 1;
   fs->prev_line_no = 1;
}

#define CHECK_NEW_LINE(c,line_no) do {\
            if (c == CHAR_LF) {  \
               line_no++; \
            } \
         } while (0)


int get_char (file_stream_t* f, char* c)
{
   assert (c);   
   assert (f);
   assert (f->file);
   assert (f->cur_pos <= FILE_BUF_SIZE);
   assert (f->total_chars <= FILE_BUF_SIZE);


   if (f->using_prev_char) {
      *c = f->prev_char;
      assert (*c);
      f->line_no = f->prev_line_no;
      f->using_prev_char = 0;
      CHECK_NEW_LINE (*c, f->line_no);
      return (int)(*c);
   } 

   if (f->cur_pos >= f->total_chars) {
      /* need to read from file to buffer. */
      f->total_chars = fread (f->buf, 1, FILE_BUF_SIZE, f->file);
      f->cur_pos = 0;
   }
   if (f->total_chars == 0) {
      /* last fread gets nothing, file ended.*/
      *c = '\0';
      return TOKEN_EOF;
   }

   *c = f->buf[f->cur_pos++];
   f->prev_line_no = f->line_no;
   if (*c == CHAR_LF) {
      f->line_no++;
   }
   f->prev_char = *c;

   return (int)(*c);
}

void put_back (file_stream_t* f)
{
   assert (f);
   assert (f->file);
   assert (f->cur_pos < FILE_BUF_SIZE);
   assert (f->total_chars <= FILE_BUF_SIZE);
   assert (!f->using_prev_char);

   f->using_prev_char = 1;
}

/*-
 * logo_scanner
 */
typedef enum tag_scan_state_t {
   StartState = 0,
   AcceptState,
   IntegerState,
   DecimalState,
   IDState,
   StringState,
   CommentState,
   GTState,
   LTState,
   EquState,
   NEquState
} scan_state_t; 

#define MAX_TOKEN_LEN 10240

typedef struct tag_logo_scanner_t {
   file_stream_t*    stream;
   char              buf[MAX_TOKEN_LEN];
   int               cur_pos;
   scan_state_t      state;
   int               token_line_no;
} logo_scanner_t;

file_stream_t g_fs;
logo_scanner_t g_scanner;


void start_scanning (FILE* file)
{
   init_file_stream (&g_fs, file);
   g_scanner.stream = &g_fs;
   memset (g_scanner.buf, 0 , MAX_TOKEN_LEN * sizeof (char));
   g_scanner.cur_pos = 0;
   g_scanner.state = StartState;
   g_scanner.token_line_no = 1;
}

int get_line_no ()
{
   return g_scanner.token_line_no;
}

const char* get_token_string ()
{
   return (const char*)g_scanner.buf;
}


/*
 * scanner helper routines
 */
static token_t get_token_r ();
static token_t start_state (logo_scanner_t* scanner, char c);
static token_t integer_state (logo_scanner_t* scanner, char c);
static token_t decimal_state (logo_scanner_t* scanner, char c);
static token_t id_state (logo_scanner_t* scanner, char c);
static token_t string_state (logo_scanner_t* scanner, char c);
static token_t comment_state (logo_scanner_t* scanner, char c);
static token_t gt_state (logo_scanner_t* scanner, char c);
static token_t lt_state (logo_scanner_t* scanner, char c);
static token_t equ_state (logo_scanner_t* scanner, char c);
static token_t nequ_state (logo_scanner_t* scanner, char c);

typedef struct tag_keyword_t {
   char*       kw;
   token_t     tk;
} keyword_t;

keyword_t g_kw[] = { {"return", TOKEN_RETURN}, {"reset", TOKEN_RESET},
 {"canvassize", TOKEN_CANVASSIZE}, {"cs", TOKEN_CANVASSIZE}, 
 {"canvascolor", TOKEN_CANVASCOLOR}, {"cc", TOKEN_CANVASCOLOR},
 {"pencolor", TOKEN_PENCOLOR}, {"pc", TOKEN_PENCOLOR},
 {"penwidth", TOKEN_PENWIDTH}, {"pw", TOKEN_PENWIDTH},
 {"go", TOKEN_GO},
 {"turnright", TOKEN_TURNRIGHT}, {"tr", TOKEN_TURNRIGHT},
 {"turnleft", TOKEN_TURNLEFT}, {"tl", TOKEN_TURNLEFT},
 {"forward", TOKEN_FORWARD}, {"fw", TOKEN_FORWARD},
 {"backward", TOKEN_BACKWARD}, {"bw", TOKEN_BACKWARD},
 {"direction", TOKEN_DIRECTION}, {"dir", TOKEN_DIRECTION},
 {"repeat", TOKEN_REPEAT},
 {"fontsize", TOKEN_FONTSIZE},
 {"print", TOKEN_PRINT},
 {"hide", TOKEN_HIDE},
 {"sh", TOKEN_HIDE},
 {"learn", TOKEN_LEARN},
 {"wait", TOKEN_WAIT},
 {"clear", TOKEN_CLEAR}, {"cr", TOKEN_CLEAR},
 {"for", TOKEN_FOR}, {"to", TOKEN_TO},
 {"if", TOKEN_IF}, {"else", TOKEN_ELSE},
 {"while", TOKEN_WHILE},
 {"penup", TOKEN_PENUP}, {"pu", TOKEN_PENUP},
 {"pendown", TOKEN_PENDOWN}, {"pd", TOKEN_PENDOWN},
 {"center", TOKEN_CENTER}, 
 {"gox", TOKEN_GOX}, {"goy", TOKEN_GOY},
 {"wrapon", TOKEN_WRAPON}, {"wrapoff", TOKEN_WRAPOFF},
 {"show", TOKEN_SHOW}, {"ss", TOKEN_SHOW}, 
 {"message", TOKEN_MESSAGE},
 {"break", TOKEN_BREAK},
 {"continue", TOKEN_CONTINUE},
 {"and", TOKEN_AND},
 {"or", TOKEN_OR},
 {"not", TOKEN_NOT},
 {NULL, -1}
}; 

void print_info(int is_debug, char* fmt, ...)
{
   va_list ap;
   assert (g_exec);

   va_start (ap, fmt);
   vsnprintf (g_info, STR_BUF_SIZE, fmt, ap);
   va_end (ap);
   if (is_debug) {
      g_exec->debug_info(g_info);
      return;
   }
   g_exec->error_info (g_info);
   return;
}

token_t get_token()
{
   token_t token;
   assert (g_exec);
   token = get_token_r();

   char buf[256];
   print_token (buf, 256, token, get_token_string());
   print_info (DEBUG_INFO, "%d\t%s\r\n", get_line_no(), buf);

   return token;
}

static token_t get_token_r()
{
   int i;
   token_t ret;
   
   assert (g_scanner.stream);
   assert (g_scanner.stream->file);
   assert (g_scanner.stream->cur_pos <= FILE_BUF_SIZE);
   assert (g_scanner.stream->total_chars <= FILE_BUF_SIZE);

   g_scanner.state = StartState;
   g_scanner.cur_pos = 0;
   memset (g_scanner.buf, 0, MAX_TOKEN_LEN * sizeof (char));

   while (g_scanner.state != AcceptState) {
      char c;

      if (get_char(g_scanner.stream, &c) == TOKEN_EOF) {
         if (g_scanner.state == StartState || g_scanner.state == CommentState) {
            return TOKEN_EOF;
         }
      }
      switch (g_scanner.state) {
      case StartState:
         ret = start_state (&g_scanner, c);
         break;
      case IntegerState:
         ret = integer_state (&g_scanner, c);
         break;
      case DecimalState:
         ret = decimal_state (&g_scanner, c);
         break;
      case IDState:
         ret = id_state (&g_scanner, c);
         break;
      case StringState:
         ret = string_state (&g_scanner, c);
         break;
      case CommentState:
         ret = comment_state (&g_scanner, c);
         break;
      case GTState:
         ret = gt_state (&g_scanner, c);
         break;
      case LTState:
         ret = lt_state (&g_scanner, c);
         break;
      case EquState:
         ret = equ_state (&g_scanner, c);
         break;
      case NEquState:
         ret = nequ_state (&g_scanner, c);
         break;

      default:
         assert(0);
      }

      if (ret < 0) {
         return ret;
      }
   }

   if (ret != TOKEN_ID) {
      return ret;
   }
   i = 0;
   while (g_kw[i].kw) {
      if (strcmp(g_kw[i].kw, g_scanner.buf) == 0) {
         return g_kw[i].tk;
      }
      i++;
   } 
   return TOKEN_ID;
}



#define IS_NUMERIC(c) (c >= '0' && c <='9')
#define IS_ALPHABETIC(c) ((c >='a' && c <='z') || (c >= 'A' && c <= 'Z'))
#define IS_SPACE(c) (c == ' ' || c == '\t' || c == CHAR_LF)

#define ADD_TO_BUF(scanner,c)  \
         do {\
            if (scanner->cur_pos < MAX_TOKEN_LEN -1) {   \
               scanner->buf[scanner->cur_pos ++] = c; \
               scanner->buf[scanner->cur_pos] = '\0'; \
            } else {    \
               return TOKEN_INVALID;   \
            }  \
         } while (0)


#define IS_OTHER_1CHAR_TOKEN(c) ((c=='+' || c=='-' || c=='*' || c=='/' ||\
                              c==',' || c=='(' || c==')' || c=='[' ||\
                              c==']' || c=='{' || c == '}'))

static token_t start_state (logo_scanner_t* scanner, char c)
{
   assert (scanner);
   assert (scanner->cur_pos == 0);

   scanner->token_line_no = scanner->stream->line_no;
   if (IS_SPACE (c)) {
      return 0;
   }

   if (!IS_SPACE(c) && c != '#') {
      ADD_TO_BUF (scanner, c);
   }

   if (IS_NUMERIC(c)) {
      scanner->state = IntegerState;
   } else if (IS_ALPHABETIC (c)) {
      scanner->state = IDState;
   } else if (c == '"') {
      scanner->state = StringState;
   } else if (c == '#') {
      scanner->state = CommentState;
   } else if (c == '>') {
      scanner->state = GTState;
   } else if (c == '<') {
      scanner->state = LTState;
   } else if (c == '=') {
      scanner->state = EquState;
   } else if (c == '!') {
      scanner->state = NEquState;
   } else if (IS_OTHER_1CHAR_TOKEN(c)) {
      scanner->state = AcceptState;
      return c;
   } else {
      scanner->state = AcceptState;
      return TOKEN_INVALID;
   }

   return 0;
}

static token_t integer_state (logo_scanner_t* scanner, char c)
{
   assert (scanner);

   if (IS_NUMERIC(c)) {
      scanner->state = IntegerState;
      ADD_TO_BUF (scanner, c);
      return 0;
   } 
   if (c == '.') {
      scanner->state = DecimalState;
      ADD_TO_BUF (scanner, c);
      return 0;
   } 

   scanner->state = AcceptState;
   if (!IS_SPACE (c) && c != '\0') {
       put_back (scanner->stream);
   }
   return TOKEN_NUMBER;
}

static token_t decimal_state (logo_scanner_t* scanner, char c)
{
   assert (scanner);

   if (IS_NUMERIC(c)) {
      scanner->state = DecimalState;
      ADD_TO_BUF (scanner, c);
      return 0;
   } 

   scanner->state = AcceptState;
   if (!IS_SPACE (c) && c != '\0') {
      put_back (scanner->stream);
   }
   return TOKEN_NUMBER;
}

static token_t id_state (logo_scanner_t* scanner, char c)
{
   assert (scanner);

   if (IS_NUMERIC(c) || IS_ALPHABETIC(c) || c == '_') {
      scanner->state = IDState;
      ADD_TO_BUF (scanner, c);
      return 0;
   }

   scanner->state = AcceptState;
   if (!IS_SPACE (c) && c != '\0') {
      put_back (scanner->stream);
   }
   return TOKEN_ID;
}

static token_t string_state (logo_scanner_t* scanner, char c)
{
   assert (scanner);

   if (c != '"' && c != CHAR_LF) {
      scanner->state = StringState;
      ADD_TO_BUF (scanner, c);
      return 0;
   }

   if (c == CHAR_LF) {
      scanner->state = AcceptState;
      return TOKEN_ERROR;
   }
   /* c == '"'*/
   ADD_TO_BUF (scanner, c);
   scanner->state = AcceptState;
   return TOKEN_STRING;
}

static token_t comment_state (logo_scanner_t* scanner, char c)
{
   assert (scanner);
   if (c != CHAR_LF) {
      scanner->state = CommentState;
   } else {
      scanner->state = StartState;
   }
   return 0;
}

static token_t gt_state (logo_scanner_t* scanner, char c)
{
   assert (scanner);
   if (c == '=') {
      scanner->state = AcceptState;
      ADD_TO_BUF (scanner, c);
      return TOKEN_GE;
   }

   scanner->state = AcceptState;
   if (!IS_SPACE (c) && c != '\0') {
      put_back (scanner->stream);
   }
   return TOKEN_GT;
}

static token_t lt_state (logo_scanner_t* scanner, char c)
{
   assert (scanner);
   if (c == '=') {
      scanner->state = AcceptState;
      ADD_TO_BUF (scanner, c);
      return TOKEN_LE;
   }

   scanner->state = AcceptState;
   if (!IS_SPACE (c) && c != '\0') {
      put_back (scanner->stream);
   }
   return TOKEN_LT;
}

static token_t equ_state (logo_scanner_t* scanner, char c)
{
   assert (scanner);
   if (c == '=') {
      scanner->state = AcceptState;
      ADD_TO_BUF (scanner, c);
      return TOKEN_EQUAL;
   }

   scanner->state = AcceptState;
   if (!IS_SPACE (c) && c != '\0') {
      put_back (scanner->stream);
   }
   return TOKEN_ASSIGN;
}

static token_t nequ_state (logo_scanner_t* scanner, char c)
{
   assert (scanner);
   if (c == '=') {
      scanner->state = AcceptState;
      ADD_TO_BUF (scanner, c);
      return TOKEN_NOT_EQUAL;
   }

   scanner->state = AcceptState;
   if (!IS_SPACE (c) && c != '\0') {
      put_back (scanner->stream);
   }
   return TOKEN_ERROR;
}

void print_token(char* buf, int len, token_t token, const char* str)
{
   assert (buf);
   assert (len);

   buf[len-1] = '\0';

   switch (token) {
   case TOKEN_RESET:
   case TOKEN_RETURN:
   case TOKEN_WAIT:
   case TOKEN_CLEAR:
   case TOKEN_HIDE:
   case TOKEN_LEARN:
   case TOKEN_FOR:
   case TOKEN_TO:
   case TOKEN_REPEAT:
   case TOKEN_WHILE:
   case TOKEN_CANVASSIZE:
   case TOKEN_CANVASCOLOR:
   case TOKEN_PENCOLOR:
   case TOKEN_PENWIDTH:
   case TOKEN_GO:
   case TOKEN_DIRECTION:
   case TOKEN_TURNRIGHT:
   case TOKEN_TURNLEFT:
   case TOKEN_FORWARD:
   case TOKEN_BACKWARD:
   case TOKEN_FONTSIZE:
   case TOKEN_PRINT:
   case TOKEN_IF:
   case TOKEN_ELSE:
   case TOKEN_CENTER:
   case TOKEN_GOX:
   case TOKEN_GOY:
   case TOKEN_PENUP:
   case TOKEN_PENDOWN:
   case TOKEN_WRAPOFF:
   case TOKEN_WRAPON:
   case TOKEN_SHOW:
   case TOKEN_MESSAGE:
   case TOKEN_AND:
   case TOKEN_OR:
   case TOKEN_NOT:
   case TOKEN_BREAK:
   case TOKEN_CONTINUE:
      _snprintf (buf, len , "reserved word: %s.", str);
      break;

   case TOKEN_EQUAL:
       _snprintf (buf, len , "Equal decision %s.", str);
      break;

   case TOKEN_NOT_EQUAL:
       _snprintf (buf, len , "Not equal decision %s.", str);
      break;

   case TOKEN_PLUS:
   case TOKEN_MINUS:
   case TOKEN_MUL:
   case TOKEN_DIV:
   case TOKEN_LT:
   case TOKEN_LE:
   case TOKEN_GT:
   case TOKEN_GE:
   case TOKEN_ASSIGN:
   case TOKEN_COMMA:
   case TOKEN_LEFT_BRACKET:
   case TOKEN_RIGHT_BRACKET:
   case TOKEN_LEFT_SQUARE:
   case TOKEN_RIGHT_SQUARE:
   case TOKEN_COMPOUND_START:
   case TOKEN_COMPOUND_END:
       _snprintf (buf, len , "%s", str);
      break;

   case TOKEN_NUMBER:
       _snprintf (buf, len , "NUM, val= %s",str);
      break;

   case TOKEN_ID:
       _snprintf (buf, len , "ID, name= %s",str);
      break;

   case TOKEN_ERROR:
       _snprintf (buf, len , "ERROR, %s", str);
      break;

   case TOKEN_STRING:
       _snprintf (buf, len , "String, %s", str);
      break;

   case TOKEN_EOF:
       _snprintf (buf, len , "END!");
      break;

   case TOKEN_INVALID:
       _snprintf (buf, len , "Invalid token or buf overflown: %s", str);
      break;

   default: /* should never happen */
      assert (0);
   }
}
